package com.flyee.utils
{
	import flash.display.DisplayObjectContainer;
	import flash.display.Loader;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.HTTPStatusEvent;
	import flash.events.IOErrorEvent;
	import flash.events.NetStatusEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.TimerEvent;
	import flash.net.URLRequest;
	import flash.net.URLStream;
	import flash.system.ApplicationDomain;
	import flash.system.LoaderContext;
	import flash.utils.ByteArray;
	import flash.utils.Timer;

	/**
	 * dispatch when a dll is installed into current domain
	 */
	[Event(name="install", type = "com.flyee.utils.DLLLoader")]

	/**
	 * dispatch when all dlls loaded and installed
	 */
	[Event(name="allCompleted", type = "com.flyee.utils.DLLLoader")]

	[Event(name="open", type = "flash.events.Event")]
	[Event(name="progress", type = "flash.events.ProgressEvent")]
	[Event(name="ioError", type = "flash.events.IOErrorEvent")]
	[Event(name="httpStatus", type = "flash.events.HTTPStatusEvent")]
	[Event(name="securityError", type = "flash.events.SecurityErrorEvent")]

	/**
	 * DLL loader will load the DLLs into current domain.
	 * you should call addDll() to add dlls in to it's work queue,
	 * and then, call notify(), the queue will be processed automatically.
	 * after all the dlls loaded and installed, it will dispatch a "allCompleted" event
	 *
	 * @author Hukuang
	 */
	public class DLLLoader extends EventDispatcher
	{

		public static const INSTALL:String = "install";
		public static const ALL_COMPLETED:String = "all completed";
		public static const MAX_RECONNECT:uint = 5;

		private var stream:URLStream;
		private var loader:Loader;
		private var root:DisplayObjectContainer;

		private var dlls:Array = null;
		private var currentDLL:DLL;
		private var dllCount:int = 0;
		private var dllLoadedCount:int = 0;

		private var startTime:Number;
		private var currentSpeed:Number;

		private const MAX_FAULT_COUNT:int = 3;
		private var faultCount:int = 0;
		private var dataObj:Object = null;

		/**
		 * 连接次数
		 */
		private var connectNum:int = 0;
		/**
		 * 加载进度时间监听
		 */
		private var progressTimer:Timer = null;
		/**
		 * 时间计数器
		 */
		private var timerCounter:int = 0;
		/**
		 * 连接失败次数计数器
		 */
		private var failedCounter:int = 0;
		/**
		 * 重新次数连接计数器
		 */
		private var reconnectNum:int = 0;

		public function DLLLoader(root:DisplayObjectContainer = null)
		{
			this.root = root;
			this.stream = new URLStream();
			this.stream.addEventListener(Event.OPEN, this.openHandler);
			this.stream.addEventListener(ProgressEvent.PROGRESS, this.progressHandler);
			this.stream.addEventListener(Event.COMPLETE, this.streamCompleteHandler);
			this.stream.addEventListener(IOErrorEvent.IO_ERROR, this.ioErrorHandler);
			this.stream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, this.securityErrorHandler);
			this.stream.addEventListener(HTTPStatusEvent.HTTP_STATUS, this.httpStatusHandler);

			this.loader = new Loader();
			this.loader.contentLoaderInfo.addEventListener(Event.COMPLETE, this.loaderCompleteHandler);

			progressTimer = new Timer(1000);
			progressTimer.start();
			progressTimer.addEventListener(TimerEvent.TIMER, timerHandler);
		}

		/**
		 * @param path the dll swf path
		 * @param path the dll name to display
		 * @param path the method to be execute when this dll is loaded, a param root will be pass to this method
		 */
		public function addDLL(path:String, displayName:String, executeMethodName:String = null):void
		{
			var dll:DLL = new DLL();
			dll.path = path;
			dll.desplayName = displayName;
			dll.executeMethodName = executeMethodName;
			if(!this.dlls){
				dlls = new Array();
			}
			this.dlls.push(dll);
			this.dllCount++;
		}

		public function notify():void
		{
			this.process();
		}


		public function getCurrentDisplayName():String
		{
			return this.currentDLL.desplayName;
		}

		public function getDLLsLoaded():uint
		{
			return this.dllLoadedCount;
		}

		public function getDLLsTotal():uint
		{
			return this.dllCount;
		}

		public function getcurrentSpeed():Number
		{
			return this.currentSpeed;
		}

		public function getVersion():String
		{
			return "0.4";
		}
		
		public function setDataObject(obj:Object):void{
			dataObj = obj;	
		}

		protected function process():void
		{
			this.currentDLL = this.dlls.shift();

			//trace ("process:" + this.currentDLL);

			if (this.currentDLL != null)
			{
				this.loadStream();
			}
			else
			{
				if(progressTimer && progressTimer.running){
					clear();
				}	
				this.dispatchEvent(new NetStatusEvent(DLLLoader.ALL_COMPLETED,false,false,dataObj));
			}
		}

		protected function loadStream():void
		{
			if(this.currentDLL){
				var request:URLRequest = new URLRequest(this.currentDLL.path);
				stream.load(request);
			}
		}

		protected function loadBytes(bytes:ByteArray):void
		{
			var context:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain);
			this.loader.loadBytes(bytes, context);
		}

		/**
		 * 控制重新连接服务器
		 * @param event
		 *
		 */
		private function timerHandler(event:TimerEvent):void
		{

			if (timerCounter == connectNum)
			{
				//掉线需要重新连接
				failedCounter++;

				if (failedCounter == 3)
				{
					failedCounter = 0;
					reconnectNum++;
					loadStream();
				}

				if (reconnectNum == DLLLoader.MAX_RECONNECT)
				{
					//网络断掉
					reconnectNum = 0;
					clear();
				}
			}
			else
			{
				timerCounter = connectNum;
			}
		}

		private function openHandler(e:Event):void
		{
			//trace ("opened");

			var now:Date = new Date();
			this.startTime = now.getTime();

			this.dispatchEvent(e);
		}

		private function ioErrorHandler(e:IOErrorEvent):void
		{
			this.faultCount++;

			if (this.faultCount > this.MAX_FAULT_COUNT)
			{
				this.dispatchEvent(e);
			}
			else
			{
				var timer:Timer = new Timer(500, 1);
				timer.addEventListener(TimerEvent.TIMER_COMPLETE, function(e:TimerEvent):void
					{
						loadStream();
					});
				timer.start();
			}
		}

		private function securityErrorHandler(e:SecurityErrorEvent):void
		{
			this.faultCount++;

			if (this.faultCount > this.MAX_FAULT_COUNT)
			{
				this.dispatchEvent(e);
			}
			else
			{
				var timer:Timer = new Timer(500, 1);
				timer.addEventListener(TimerEvent.TIMER_COMPLETE, function(e:TimerEvent):void
					{
						loadStream();
					});
				timer.start();
			}
		}

		private function httpStatusHandler(e:HTTPStatusEvent):void
		{
			this.dispatchEvent(e);
		}

		private function progressHandler(e:ProgressEvent):void
		{
			//trace ("progress " + Math.round(100 * e.bytesLoaded / e.bytesTotal) + "%");

			var now:Date = new Date();
			var elapsedTime:Number = now.getTime() - this.startTime;
			this.currentSpeed = e.bytesLoaded / elapsedTime;
			connectNum++;
			this.dispatchEvent(e);
		}

		private function streamCompleteHandler(e:Event):void
		{
			//trace ("dll streamed");

			var bytes:ByteArray = new ByteArray();
			var length:int = this.stream.bytesAvailable;
			this.stream.readBytes(bytes, 0, length);
			this.stream.close();

			//trace ("dll byted");
			this.dispatchEvent(new Event(DLLLoader.INSTALL));

			//install dll
			this.loadBytes(bytes);
		}

		private function loaderCompleteHandler(e:Event):void
		{
			//trace ("dll installed");

			this.dllLoadedCount++;
			this.dispatchEvent(e);

			if (currentDLL.executeMethodName != null)
			{
				var content:* = this.loader.content;
				content[currentDLL.executeMethodName](root);
			}
			//load next
			this.process();
		}

		private function clear():void
		{
			progressTimer.stop();
			progressTimer.removeEventListener(TimerEvent.TIMER, timerHandler);
			progressTimer = null;
			//stream.close();

			//loader.close();

			/* while(root.numChildren > 0)
			{
				root.removeChildAt(0);
			}
			dlls.splice(0);
			dlls = null;
			currentDLL = null;
			root = null;
			loader = null;
			stream = null; */
		}
	}
}

class DLL
{
	public var path:String;
	public var desplayName:String;
	public var executeMethodName:String;
}

